use super::mutex::{LockResult, Mutex, MutexGuard};
use core::ffi::c_long;
use core::ptr::null_mut;
use core::time::Duration;
use libc::{pthread_cond_t, pthread_mutex_t};

pub struct Condvar<T: Send> {
    mtx: Mutex<T>,
    cond: pthread_cond_t,
}

unsafe impl<T: Send> Send for Condvar<T> {}
unsafe impl<T: Send> Sync for Condvar<T> {}

impl<T: Send> Condvar<T> {
    pub fn new(data: T) -> Self {
        let mut my = Self {
            mtx: Mutex::new(data),
            cond: unsafe { core::mem::zeroed() },
        };

        unsafe { libc::pthread_cond_init(&mut my.cond, null_mut()) };
        my
    }

    pub fn notify_one(&self) {
        let _lock = self.mtx.lock().unwrap();
        unsafe {
            libc::pthread_cond_signal(self.id());
        }
    }

    /// Awakens all current waiters on this condition variable.
    #[inline]
    pub fn notify_all(&self) {
        let _lock = self.mtx.lock().unwrap();
        unsafe {
            libc::pthread_cond_broadcast(self.id());
        }
    }

    pub fn wait_timeout(&self, dur: Duration) -> LockResult<MutexGuard<'_, T>> {
        unsafe {
            let lock = self.mtx.lock().unwrap();
            let mut now: libc::timespec = core::mem::zeroed();
            libc::clock_gettime(libc::CLOCK_REALTIME, &mut now);
            let ms = dur.as_millis();
            let tms = now.tv_sec as u128 * 1000 + now.tv_nsec as u128 / 1_000_000 + ms;
            let mut target = libc::timespec {
                tv_sec: (tms / 1000) as libc::time_t,
                tv_nsec: (tms % 1000 * 1000_000) as c_long,
            };
            libc::pthread_cond_timedwait(self.id(), self.mtx.id(), &mut target);
            Ok(lock)
        }
    }

    pub fn lock(&self) -> LockResult<MutexGuard<'_, T>> {
        self.mtx.lock()
    }

    pub(crate) unsafe fn id(&self) -> *mut pthread_cond_t {
        &self.cond as *const pthread_cond_t as *mut pthread_cond_t
    }

    pub(crate) unsafe fn mtx_id(&self) -> *mut pthread_mutex_t {
        self.mtx.id()
    }
}

impl<T: Send> Drop for Condvar<T> {
    fn drop(&mut self) {
        self.notify_all();
        unsafe {
            libc::pthread_cond_destroy(&mut self.cond);
        }
    }
}
